﻿/* 
* Copyright (c) 2009, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using Bio.Data.Interfaces;
using Bio.Data.Providers.Virtualization;

namespace Bio.Data.Providers.Alignment.AE2
{
    /// <summary>
    /// This reads a FastA file sequence
    /// </summary>
    [DebuggerDisplay("{Id}:{Name}:{ScientificName}")]
    internal class VirtualizedAE2Sequence : BioExtensiblePropertyBase, IBioSequence, IVirtualizingListDataProvider<IBioSymbol>
    {
        private readonly AE2File _container;
        private int _forcedCount;
        internal int _dataCount;
        internal long _blockLength;
        internal List<Tuple<int, long>> _dataPositions;

        /// <summary>
        /// The unique identifier for this biological entity
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// The unique identifier or the course for this entity (not used).
        /// </summary>
        public string SourceId { get { return string.Empty; } }

        /// <summary>
        /// The common name for this biological entity (may be same as SciName)
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Scientific name
        /// </summary>
        public string ScientificName { get; set; }

        /// <summary>
        /// Taxonomy identifier
        /// </summary>
        public string TaxonomyId { get; set; }

        /// <summary>
        /// This represents the validator used to validate the entity
        /// </summary>
        public IBioValidator Validator { get; private set; }

        /// <summary>
        /// The first column in the data stream with data
        /// </summary>
        public int FirstDataColumn { get; private set; }

        /// <summary>
        /// Unaligned sequence of data
        /// </summary>
        public IList<IBioSymbol> SequenceData { get; private set; }

        internal VirtualizedAE2Sequence(AE2File owner, IBioValidator validator)
        {
            Validator = validator;
            SequenceData = new VirtualizingList<IBioSymbol>(this, 1024, 60);
            _container = owner;
            _dataPositions = new List<Tuple<int, long>>();
        }

        /// <summary>
        /// This is used to force the stream to a specific size.
        /// </summary>
        /// <param name="count"></param>
        internal void ForceCount(int count)
        {
            if (count < _dataCount)
                throw new ArgumentOutOfRangeException("Count may not be less than real data size");
            _forcedCount = count;
        }

        /// <summary>
        /// Returns the total known count of items
        /// </summary>
        public int TotalCount { get { return _forcedCount; } }

        /// <summary>
        /// Loads a block of the data bounded by a starting index and count.
        /// </summary>
        /// <param name="startIndex"></param>
        /// <param name="count"></param>
        /// <returns>Enumerable range</returns>
        public IEnumerable<IBioSymbol> LoadRange(int startIndex, int count)
        {
            // Bound the read count
            if (startIndex + count > _forcedCount)
                count = _forcedCount - startIndex;

            // If they are requesting the end of the data, just return none.
            if (startIndex > _dataCount)
                return Enumerable.Repeat(BioSymbol.None, count);

            // Find the starting block using our indexes.
            int startMapPosition = 0;
            int skipCount = 0;
            for (int i = 0; i < _dataPositions.Count; i++)
            {
                if (_dataPositions[i].Item1 == startIndex)
                {
                    startMapPosition = i;
                    break;
                }
                else if (_dataPositions[i].Item1 > startIndex)
                {
                    startMapPosition = i > 0 ? i - 1 : i;
                    skipCount = (i > 0) ? startIndex - _dataPositions[i - 1].Item1 : 0;
                    break;
                }
            }

            Debug.Assert(skipCount >= 0);
            Debug.Assert(startMapPosition >= 0 && startMapPosition < _dataPositions.Count);

            // Build a string of the nucelotides.
            StringBuilder sb = new StringBuilder();

            for (int i = startMapPosition; i < _dataPositions.Count; i++)
            {
                if (sb.Length >= count)
                    break;

                if (_dataPositions[i].Item1 > sb.Length)
                    sb.Append(Enumerable.Repeat('~', _dataPositions[i].Item1));

                using (var fs = _container.MmFile.CreateViewStream(_dataPositions[i].Item2, 0, MemoryMappedFileAccess.Read))
                {
                    string line = fs.ReadLine();
                    for (int x = 0; x < line.Length;)
                    {
                        char ch = line[x];
                        if (ch == '\\')
                        {
                            ch = AE2File.ConvertOctal(new[] { line[x + 1], line[x + 2], line[x + 3] });
                            x += 4;
                        }
                        else x++;
                        sb.Append(ch);
                    }
                }
            }

            // Return the sequence - skip the elements we don't want, grab all the loaded data and pad to the required count
            return sb.ToString()
                .Skip(skipCount)
                .Select(ch => AE2File.CharacterToBioSymbol(Validator, ch))
                .Take(count)
                .PadRight(BioSymbol.None, count);
        }
    }
}